Skip to content

Add Android native port for OpenLessAdd Android native port for OpenLess#447

Open
HKLHaoBin wants to merge 3 commits into
Open-Less:betafrom
HKLHaoBin:feat/openless-android-port
Open

Add Android native port for OpenLessAdd Android native port for OpenLess#447
HKLHaoBin wants to merge 3 commits into
Open-Less:betafrom
HKLHaoBin:feat/openless-android-port

Conversation

@HKLHaoBin
Copy link
Copy Markdown
Contributor

@HKLHaoBin HKLHaoBin commented May 15, 2026

User description

from #278

User description

Summary

This PR adds a native Android port of OpenLess under openless-android/.

The Android implementation ports the desktop dictation pipeline into Java/Android-native components, including:

  • floating trigger service replacing desktop hotkeys
  • microphone foreground service flow
  • Volcengine streaming ASR
  • Whisper-compatible ASR
  • OpenAI-compatible polish / translation / Q&A
  • IME insertion with clipboard fallback
  • history, dictionary, settings, model list, Q&A, and error/detail screens
  • Android-side build / verify / deploy scripts and release checklists

Included

  • new Android app module in openless-android/
  • native Activities for:
    • main screen
    • settings
    • dictionary
    • history detail
    • model list
    • Q&A
    • error detail
  • floating capsule service and notification actions
  • Android Keystore-backed secure storage
  • documentation for build, release, QA, and port status

Validation

Verified locally with:

  • .\build.ps1
  • .\verify.ps1

Also completed one emulator validation round for:

  • main sections
  • settings
  • dictionary
  • Q&A page
  • APK install / launch

Notes

This is a large additive PR because the Android port is introduced as a new directory rather than modifying the existing desktop implementation.

Known remaining work is mainly around:

  • real-device / real-credential end-to-end validation
  • release signing and store submission assets
  • further UI parity and final polish

PR Type

Enhancement, Documentation


Description

  • Android port with build/verify/deploy scripts and version sync

  • Main dictation flow: floating trigger, mic recording, streaming ASR

  • Polish, translation, and IME insertion with clipboard fallback

  • History, dictionary, settings, Q&A, and error detail screens


Diagram Walkthrough

flowchart LR
  A["build.ps1 / verify.ps1 / deploy.ps1"] -- "generates" --> B["APK"]
  B --> C["MainActivity"]
  C --> D["FloatingTriggerService"]
  C --> E["AudioRecorder"]
  E --> F["VolcengineStreamingSession"]
  F --> G["OpenAiPolishProvider"]
  G --> H["TextInserter (IME/Clipboard)"]
  C --> I["HistoryStore / DictionaryStore"]
  C --> J["SettingsActivity / QaPanelActivity"]
Loading

File Walkthrough

Relevant files
Configuration changes
3 files
build.ps1
PowerShell build script for APK generation                             
+126/-0 
deploy.ps1
ADB deployment script for device install                                 
+77/-0   
version.ps1
Version sync script from desktop app                                         
+50/-0   
Tests
1 files
verify.ps1
APK verification script checking signing and permissions 
+109/-0 
Enhancement
15 files
MainActivity.java
Main UI with dictation, history, settings, and tools sections
+1544/-0
QaPanelActivity.java
Q&A panel with voice question and streaming answers           
+754/-0 
ProviderDiagnostics.java
LLM and ASR configuration validation utilities                     
+163/-0 
SettingsActivity.java
Settings screen for providers and preferences                       
+1004/-0
DictionaryActivity.java
Dictionary management for hotwords                                             
+492/-0 
FloatingTriggerService.java
Foreground service for floating dictation trigger               
+389/-0 
HistoryDetailActivity.java
Detail view for individual history entries                             
+323/-0 
ModelListActivity.java
LLM model listing activity                                                             
+229/-0 
AudioRecorder.java
Audio recording and PCM buffer management                               
+138/-0 
VolcengineStreamingSession.java
Streaming ASR via Volcengine WebSocket                                     
+218/-0 
WhisperAsrProvider.java
Whisper-compatible ASR via HTTP                                                   
+53/-0   
OpenAiPolishProvider.java
Polish/translation using OpenAI-compatible API                     
+208/-0 
TextInserter.java
IME and clipboard insertion helper                                             
+63/-0   
HistoryStore.java
Persistent dictation history storage                                         
+155/-0 
SettingsStore.java
Secure settings storage with Android Keystore                       
+155/-0 
Error handling
1 files
ErrorDetailActivity.java
Error detail screen for troubleshooting                                   
+259/-0 
Additional files
36 files
AndroidManifest.xml +81/-0   
ISSUES_FOR_NEXT_SESSION.md [link]   
PORT_STATUS.md +168/-0 
QA_CHECKLIST.md +77/-0   
README.md +146/-0 
RELEASE.md +83/-0   
STORE_SUBMISSION_CHECKLIST.md +54/-0   
ic_launcher_foreground.xml +19/-0   
colors.xml +18/-0   
strings.xml +15/-0   
styles.xml +11/-0   
openless_input_method.xml +4/-0     
AndroidDictationCoordinator.java +278/-0 
AndroidPermissionDiagnostics.java +135/-0 
AsrProvider.java +5/-0     
CapsuleState.java +21/-0   
DictationPhase.java +8/-0     
DictionaryStore.java +232/-0 
InsertStatus.java +25/-0   
OpenLessClient.java +16/-0   
OpenLessHttp.java +43/-0   
OpenLessInputMethodService.java +72/-0   
OpenLessPrompts.java +177/-0 
PermissionStatus.java +23/-0   
PolishMode.java +29/-0   
PolishProvider.java +8/-0     
ProcessTextActivity.java +37/-0   
QaAnswerProvider.java +26/-0   
QaChatMessage.java +11/-0   
QaSessionStore.java +73/-0   
RawTranscript.java +11/-0   
SecureValueStore.java +103/-0 
SimpleWebSocket.java +208/-0 
VolcengineAsrProvider.java +206/-0 
VolcengineFrameCodec.java +111/-0 
WavEncoder.java +54/-0   


PR Type

Enhancement, Documentation


Description

  • Adds native Android OpenLess app

  • Implements dictation, QA, and IME flows

  • Adds build, verify, deploy scripts

  • Documents release, QA, port status


Diagram Walkthrough

flowchart LR
  V["version.ps1 derives Android versioning"] --> B["build.ps1 compiles and signs APK"]
  B --> P["verify.ps1 checks APK metadata"]
  B --> D["deploy.ps1 installs to device"]
  A["Android app module"] --> M["MainActivity and core flows"]
  A --> Q["QaPanelActivity voice question UI"]
  A --> S["SettingsStore and settings screens"]
  A --> F["FloatingTriggerService overlay trigger"]
  A --> I["OpenLessInputMethodService IME insertion"]
  A --> R["Docs, checklists, and manifest"]
Loading

File Walkthrough

Relevant files
Configuration changes
6 files
build.ps1
Add Android APK build pipeline                                                     
+126/-0 
verify.ps1
Validate APK metadata and permissions                                       
+109/-0 
deploy.ps1
Install and launch Android builds                                               
+77/-0   
version.ps1
Derive Android version from desktop                                           
+50/-0   
AndroidManifest.xml
Declare Android permissions and components                             
+81/-0   
release-tauri.yml
Adjust release workflow indexing                                                 
+1/-0     
Enhancement
28 files
MainActivity.java
Build Android dictation home screen                                           
+1544/-0
QaPanelActivity.java
Add conversational QA panel UI                                                     
+754/-0 
SettingsStore.java
Persist Android settings securely                                               
+155/-0 
AsrProvider.java
Define ASR provider interface                                                       
+5/-0     
SettingsActivity.java
Add Android settings screen                                                           
+1004/-0
DictionaryActivity.java
Manage dictionary entries on Android                                         
+492/-0 
FloatingTriggerService.java
Run floating trigger overlay service                                         
+390/-0 
HistoryDetailActivity.java
Show detailed history records                                                       
+323/-0 
AndroidDictationCoordinator.java
Coordinate native dictation flow                                                 
+278/-0 
ModelListActivity.java
List available LLM models                                                               
+229/-0 
SimpleWebSocket.java
Add lightweight websocket client                                                 
+208/-0 
OpenLessPrompts.java
Centralize prompt templates                                                           
+177/-0 
VolcengineAsrProvider.java
Implement Volcengine ASR client                                                   
+206/-0 
OpenAiPolishProvider.java
Implement LLM polish and translate                                             
+208/-0 
VolcengineStreamingSession.java
Stream audio into ASR sessions                                                     
+218/-0 
DictionaryStore.java
Persist and score hotwords                                                             
+232/-0 
ProviderDiagnostics.java
Add provider validation helpers                                                   
+163/-0 
HistoryStore.java
Store Android dictation history                                                   
+155/-0 
AndroidPermissionDiagnostics.java
Report Android permission readiness                                           
+135/-0 
AudioRecorder.java
Capture microphone audio streams                                                 
+138/-0 
VolcengineFrameCodec.java
Encode Volcengine websocket frames                                             
+111/-0 
SecureValueStore.java
Store secrets in Android Keystore                                               
+103/-0 
WhisperAsrProvider.java
Implement Whisper-compatible transcription                             
+53/-0   
OpenLessInputMethodService.java
Add Android IME insertion path                                                     
+72/-0   
TextInserter.java
Insert text with clipboard fallback                                           
+63/-0   
QaSessionStore.java
Track QA conversation state                                                           
+73/-0   
WavEncoder.java
Encode audio to WAV format                                                             
+54/-0   
ic_launcher_foreground.xml
Add Android launcher foreground asset                                       
+19/-0   
Documentation
5 files
PORT_STATUS.md
Document Android port progress                                                     
+168/-0 
README.md
Explain Android build and usage                                                   
+146/-0 
QA_CHECKLIST.md
Add Android QA checklist                                                                 
+77/-0   
RELEASE.md
Document Android release steps                                                     
+83/-0   
STORE_SUBMISSION_CHECKLIST.md
Prepare store submission checklist                                             
+54/-0   
Error handling
1 files
ErrorDetailActivity.java
Present structured Android errors                                               
+259/-0 
Additional files
17 files
ISSUES_FOR_NEXT_SESSION.md [link]   
colors.xml +18/-0   
strings.xml +15/-0   
styles.xml +11/-0   
openless_input_method.xml +4/-0     
CapsuleState.java +21/-0   
DictationPhase.java +8/-0     
InsertStatus.java +25/-0   
OpenLessClient.java +16/-0   
OpenLessHttp.java +43/-0   
PermissionStatus.java +23/-0   
PolishMode.java +29/-0   
PolishProvider.java +8/-0     
ProcessTextActivity.java +37/-0   
QaAnswerProvider.java +26/-0   
QaChatMessage.java +11/-0   
RawTranscript.java +11/-0   

HKLHaoBin and others added 3 commits May 15, 2026 18:17
Co-authored-by: Cursor <cursoragent@cursor.com>
…droid-port)

Co-authored-by: Cursor <cursoragent@cursor.com>
在 FloatingTriggerService 而非 MicBubbleView 中跟踪手势开始时间,以确保长按检测的准确性。此前,MicBubbleView 中的 downAt 时间戳可能会过期,导致 ACTION_UP 时协调器行为异常。同时增加了适当的 ACTION_CANCEL 处理,用于重置手势状态,防止过时的计时数据影响后续手势。
@github-actions
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Release signing

When -Configuration release is used without all keystore parameters, the script silently falls back to the debug keystore and still emits OpenLessAndroid-release.apk. That produces a release-named APK that is not release-signed, which can break store submission and make later installs fail if it is expected to replace a properly signed build.

if ($UseCustomKeystore) {
    Invoke-Checked { & $ApkSigner sign `
        --ks $KeystorePath `
        --ks-key-alias $KeystoreAlias `
        --ks-pass ("pass:" + $StorePass) `
        --key-pass ("pass:" + $KeyPass) `
        --out $Signed `
        $Aligned }
} else {
    if (-not (Test-Path $Keystore)) {
        keytool -genkeypair `
            -keystore $Keystore `
            -storepass android `
            -keypass android `
            -alias androiddebugkey `
            -keyalg RSA `
            -keysize 2048 `
            -validity 10000 `
            -dname "CN=Android Debug,O=Android,C=US" | Out-Null
    }

    Invoke-Checked { & $ApkSigner sign `
        --ks $Keystore `
        --ks-pass pass:android `
        --key-pass pass:android `
        --out $Signed `
        $Aligned }
Stale opacity

Disabled mode buttons are dimmed, but the enabled branch never restores their alpha. If a mode is disabled in settings and later re-enabled, the button stays semi-transparent even though it is active, which makes the control look broken and harder to use.

private void refreshModeButtons() {
    if (modeRow == null) return;
    for (int i = 0; i < modeRow.getChildCount(); i++) {
        View child = modeRow.getChildAt(i);
        if (child instanceof Button && child.getTag() instanceof PolishMode) {
            Button b = (Button) child;
            PolishMode mode = (PolishMode) b.getTag();
            boolean enabled = isModeEnabled(mode);
            boolean active = mode == settings.mode;
            b.setEnabled(enabled);
            b.setTextColor(enabled && active ? Color.WHITE : OL_INK_3);
            if (enabled && active) b.setBackgroundDrawable(pillBg(OL_BLUE));
            else if (enabled) b.setBackgroundDrawable(outlineBg(OL_LINE_STRONG));
            else { b.setBackgroundDrawable(outlineBg(OL_LINE)); b.setAlpha(0.4f); }
        }
    }

@appergb
Copy link
Copy Markdown
Collaborator

appergb commented May 16, 2026

@claude 应该不会对现有代码造成破坏性更改吧?如果没有的话,就可以合并了,帮我审查一下。

@appergb
Copy link
Copy Markdown
Collaborator

appergb commented May 17, 2026

@claude 这个安卓版主要关注以下几点:

  1. 它主要更改了什么?
    1. 它会不会对现有代码造成破坏?
    1. 这个安卓版是否会影响其他平台?

@claude
Copy link
Copy Markdown

claude Bot commented May 17, 2026

Claude finished @appergb's task in 2m 2s —— View job


Android 端口 PR 审查结果

审查任务清单

  • 读取仓库配置 (CLAUDE.md)
  • 分析主要更改内容
  • 检查对现有代码的影响
  • 分析跨平台影响
  • 汇总审查结果

1. 主要更改内容

这个 PR 在仓库中新增了一个完全独立的 openless-android/ 目录,包含:

类别 文件 说明
主界面 MainActivity.java (1544行) 听写、历史、工具三栏导航,程序主入口
设置 SettingsActivity.java (1004行) ASR / LLM / 热词等所有配置
悬浮触发 FloatingTriggerService.java (390行) 前台 Service,绘制悬浮气泡,替代桌面端全局热键
听写协调器 AndroidDictationCoordinator.java (278行) 对应桌面端 coordinator.rs 的状态机
ASR 实现 VolcengineAsrProvider.java / WhisperAsrProvider.java 火山引擎流式 WS + Whisper HTTP 两路
润色/翻译 OpenAiPolishProvider.java OpenAI 兼容接口
问答面板 QaPanelActivity.java (754行) 语音提问 + 流式回答
安全存储 SecureValueStore.java Android Keystore + AES-GCM,与桌面端 Keychain/WinCred 隔离
持久化 HistoryStore, DictionaryStore, SettingsStore JSON 文件 + SharedPreferences
IME OpenLessInputMethodService.java 系统输入法路径(可选),TextInserter 含剪贴板兜底
构建脚本 build.ps1, verify.ps1, deploy.ps1, version.ps1 纯 PowerShell,不依赖 Gradle
文档 5 个 .md 文件 构建、发布、QA、商店提交、端口状态

现有文件唯一修改.github/workflows/release-tauri.yml — 仅在第 2 行新增了一行注释 # meta: ensure Actions indexes this workflow on forks (no behavior change).,无任何逻辑变更。


2. 是否会破坏现有代码?

不会。 这是一个纯增量 PR,所有 57 个 Android 相关文件均为新增,0 个删除。

  • 桌面端代码(openless-all/app/,Tauri + Rust + React)完全未被触碰
  • CI 工作流只加了一行注释,行为不变
  • version.ps1只读桌面端的 package.json / tauri.conf.json / Cargo.toml 来同步版本号,不会写入这些文件

3. 是否影响其他平台?

不影响。 macOS / Windows / Linux 桌面端完全隔离:

  • 不同代码目录,不同构建工具链(javac/d8 vs cargo/npm
  • 不共享任何源文件
  • 发布流程独立(Android APK vs Tauri .dmg/.msi/.deb

需要关注的问题

问题 1:release 构建静默回退 debug 签名build.ps1:58,94-121

当传入 -Configuration release 但未提供完整 Keystore 参数时,脚本会回退到自动生成的 debug keystore,但输出文件仍命名为 OpenLessAndroid-release.apk。这会导致一个非正式签名的 APK 带着"release"的名字,在商店更新场景可能导致安装失败。

建议:当 -Configuration release 但 Keystore 参数缺失时,直接 throw 而非静默降级。Fix this →

问题 2:模式按钮 alpha 值未恢复MainActivity.java:1138

refreshModeButtons() 在禁用状态设置 setAlpha(0.4f),但启用分支没有调用 setAlpha(1.0f) 恢复。若用户先禁用某模式再重新启用,按钮会保持半透明状态。Fix this →

问题 3:build-tools 版本硬编码build.ps1:26

build-tools\34.0.0 硬编码,若开发者本地只有其他版本(如 35.x)会直接报错。可考虑动态查找最新版本。

问题 4:ISSUES_FOR_NEXT_SESSION.md 不应包含在 PR 中

这是开发过程中的临时笔记文件,建议从 PR 中移除。

注意事项:Android 版没有任何自动化测试,目前仅靠手工验证(模拟器)。CI 也不会自动构建 Android APK(无 Gradle / Android CI job)。这些在 PR 描述中已说明为已知遗留工作。


结论:合并安全,不会破坏桌面端任何现有功能。上述问题 1 和 2 建议在合并前或后续 PR 中修复,问题 3、4 属于改进建议。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants